﻿#region

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using Ionic.Zip;
using MediaSage.Lib.Configuration;
using MediaSage.Lib.Database;
using MediaSage.Lib.Utilities;

#endregion

namespace MediaSage.Lib.Plugin
{
    /// <summary>
    ///     Class for installing plugins
    /// </summary>
    public class Installer
    {
        #region Fields

        private readonly Config config;
        private readonly Logger logger;

        #endregion Fields

        #region Constructors

        public Installer(Config Configuration)
        {
            config = Configuration;
            logger = config.Logger;
            logger.Log("Plugin.Installer Initalized");
        }

        #endregion Constructors

        #region Methods

        /// <summary>
        ///     This will:
        ///     1. unzip the plugin zip file
        ///     2. Read read an XML file called Install.xml which will have:
        ///     A. Plugin Name
        ///     B. Plugin main DLL
        ///     C. Plugin Version
        ///     D. Plugin Type
        /// </summary>
        /// <param name="InstallationFile"> </param>
        /// <returns> true=success </returns>
        // TODO: Add Rollbacks for database stuff
        // TODO: Release the plugin DLL before deleting DIr
        // TODO: Make this able to detect a reinstall
        public bool InstallPlugin(string InstallationFile, bool Reinstall)
        {
            logger.Log("Entering InstallPlugin(" + InstallationFile + "," + Reinstall.ToString() + ")");

            string PluginName = string.Empty;
            string PluginDLL = string.Empty;
            int Major = 0;
            int Minor = 0;
            int BuildNumber = 1;
            var Type = PluginType.file;
            bool installerFile = false;
            int Priority = 100;
            bool reinstall = false;
            InstallXML.IDType objectIDType = InstallXML.IDType.file;

            if (!File.Exists(InstallationFile))
            {
                logger.Log(Logger.Levels.ERROR, string.Format("{0} Plugin source file does not exist", InstallationFile));
                logger.Log("Exiting InstallPlugin(" + InstallationFile + "), rc==false");
                return false;
            }

            var pe = new PluginEntity();

            string DestinationDir = config.PluginDirectory + @"\" + pe.ID;

            try
            {
                using (ZipFile installFile = ZipFile.Read(InstallationFile))
                {
                    foreach (ZipEntry zipEntry in installFile)
                    {
                        logger.Log("Extracting: " + zipEntry.FileName);
                        if (zipEntry.FileName.ToUpper().Equals("INSTALLER.XML"))
                        {
                            var root = new XmlRootAttribute("root");
                            var ser = new XmlSerializer(typeof(InstallXML), root);
                            InstallXML installFileData;
                            Stream s = installFile[zipEntry.FileName].OpenReader();
                            installFileData = (InstallXML)ser.Deserialize(new StreamReader(s));
                            PluginName = installFileData.Name;
                            logger.Log("Plugin Name: " + PluginName);
                            PluginDLL = installFileData.MainDLL;
                            logger.Log("Plugin Main DLL: " + PluginDLL);
                            Major = installFileData.MajorVersion;
                            Minor = installFileData.MinorVersion;
                            BuildNumber = installFileData.BuildNumber;
                            Priority = installFileData.Priority;
                            Type = installFileData.Type;
                            objectIDType = installFileData.IdType;

                            logger.Log(string.Format("Plugin Verison: {0}.{1}.{2}", Major, Minor, BuildNumber));
                            installerFile = true;
                        }
                        else
                        {
                            zipEntry.Extract(DestinationDir, ExtractExistingFileAction.OverwriteSilently);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, "Exception reading installation bundle, exception: " + e.Message);
                logger.Log("Exiting InstallPlugin(" + InstallationFile + "), rc==false");
                try
                {
                    Directory.Delete(DestinationDir, true);
                }
                catch (Exception ex)
                {
                    logger.Log("Exception cleaning up: " + ex.Message);
                }
                return false;
            }

            System.Threading.Thread.Sleep(1500);
            if (!installerFile)
            {
                logger.Log(Logger.Levels.ERROR, "No installer file in zip, aborting install");
                try
                {
                    Directory.Delete(DestinationDir, true);
                }
                catch (Exception ex)
                {
                    logger.Log("Exception cleaning up: " + ex.Message);
                }

                logger.Log("Exiting InstallPlugin(" + InstallationFile + "), rc==false");
                return false;
            }

            // Check for things missing from the install file that we will need
            if (PluginName == null || PluginName.Length == 0)
            {
                logger.Log(Logger.Levels.ERROR, "PluginName not defined in the install file, aborting install");
                return false;
            }

            PluginEntity dPE = Utilities.Plugin.GetConfigurationByName(pe.Name);
            if (dPE != null)
            {
                reinstall = true;
                if (Reinstall == false && dPE.UpdateAutomatically == false)
                {
                    logger.Log("Plugin already installed, aborting install with Reinstall=false");
                    return false;
                }

                // If the installation suceeds we will delete the original plugin data on disk and in DB, if it fails we will will just not save this new one
                // and delete the data
            }

            // now we need to create our Plugin Entity, get the GUID, save it to the config and call setdefaultconfig and extensions handled (To add to the base extensions handled)
            pe.Name = PluginName;
            pe.Location = Path.Combine(DestinationDir, PluginDLL);
            pe.PluginType = Type;
            pe.Priority = Priority;
            pe.IsActive = true;
            pe.IDObjectType = (PluginIDObjectType)objectIDType;
            pe.SaveOrUpdate();

            // Now load the instance
            logger.Log(string.Format("Loading {0}", pe.Name));
            PluginBase plugin = config.PluginManager.GetPlugin(pe);
            if (plugin == null)
            {
                logger.Log(Logger.Levels.ERROR, "Could not load Plugin DLL, cleaning up and exiting");
                try
                {
                    Directory.Delete(DestinationDir, true);
                }
                catch (Exception ex)
                {
                    logger.Log("Exception cleaning up: " + ex.Message);
                }
                return false;
            }

            try
            {
                logger.Log(string.Format("Setting default config for: ", pe.Name));
                plugin.SetDefaultConfig();
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR,
                           "Could not set Plugin default Config Exception: " + e.Message + ", cleaning up and exiting");
                try
                {
                    Directory.Delete(DestinationDir, true);
                    DBUtil.GetSession().Delete(pe);
                }
                catch (Exception ex)
                {
                    logger.Log("Exception cleaning up: " + ex.Message);
                }
                return false;
            }

            if (pe.ExtensionsHandled == null)
            {
                pe.ExtensionsHandled = new List<MediaFileTypesEntity>();
            }

            // Now add extensions for plugin to the main extensions
            var ext = new List<string>();
            foreach (MediaFileTypesEntity fe in config.MediaTypes)
            {
                ext.Add(fe.Extension.ToUpper());
            }

            foreach (MediaFileTypesEntity fe in pe.ExtensionsHandled)
            {
                if (!ext.Contains(fe.Extension.ToUpper()))
                {
                    logger.Log("Adding extention: " + fe.Extension + " to master configuration");
                    config.MediaTypes.Add(fe);
                }
            }

            // Add the plugin to the Config

            config.Plugins.Add(pe);

            // If its a reinstall remove the original plugin from the config, remove the DB entry, and clean up the disk
            if (reinstall)
            {
                UninstallPlugin(dPE);
            }
            config.WriteConfig();

            logger.Log("Exiting InstallPlugin(" + InstallationFile + "), rc==true");
            return true;
        }

        public bool UninstallPlugin(PluginEntity PE)
        {
            logger.Log("Entering UninstallPlugin(" + PE.Name + ")");
            config.PluginManager.UnloadPlugin(PE);

            logger.Log("Removing files for " + PE.Name + ")");
            try
            {
                Directory.Delete(Path.GetDirectoryName(PE.Location), true);
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, "Error removing plugin files: " + e.Message);
                return false;
            }

            // Now we are going to remove the Config DB item
            try
            {
                PE.Delete();
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, "Error removing plugin from database: " + e.Message);
                return false;
            }

            logger.Log(String.Format("Plugin: {0} Version {1}.{2}.{3} removed successfully", PE.Name, PE.MajorVersion,
                                     PE.MinorVersion, PE.BuildNumber));
            logger.Log("Exiting UninstallPlugin(" + PE.Name + ")");
            return true;
        }

        #endregion Methods
    }
}